package cn.cityre.tools.db;

import com.google.common.base.Preconditions;
import com.google.common.base.Splitter;
import com.google.common.base.Strings;
import org.apache.commons.dbcp.BasicDataSource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.config.RuntimeBeanReference;
import org.springframework.beans.factory.support.*;
import org.springframework.context.EnvironmentAware;
import org.springframework.core.env.Environment;
import org.springframework.core.io.ClassPathResource;
import org.springframework.stereotype.Component;

import java.io.*;
import java.util.HashMap;
import java.util.List;
import java.util.Map;


/**
 * @author sunzhaoye
 */
@Component
public class DynamicCreateDataSourceBean implements BeanDefinitionRegistryPostProcessor, EnvironmentAware {

	final static private Logger log = LoggerFactory.getLogger(DynamicCreateDataSourceBean.class);

	private String citiesFilename;

	@Override
	public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {

	}

	@Override
	public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {

		// create DataSource Bean
		Map<String, DataSourceInfo> sources = parsePropertiesFile();
		if (!sources.isEmpty()) {
			ManagedMap<String, RuntimeBeanReference> mm = new ManagedMap<>();

			log.info("");
			log.info("Mysql source difinitaion start");
			String defaultName = null;

			for (Map.Entry<String, DataSourceInfo> entry : sources.entrySet()) {
				
				registry.registerBeanDefinition("DB-" + entry.getKey(),
						dataSourceBeanDifinition(entry.getKey(), entry.getValue()));
				mm.put(entry.getKey(), new RuntimeBeanReference("DB-" + entry.getKey()));
				if (Strings.isNullOrEmpty(defaultName)) {
					defaultName = "DB-" + entry.getKey();
				}
				log.info("Create bean {}", "DB-" + entry.getKey());
			}

			log.info("Mysql source difinitaion end");
			log.info("");

			Preconditions.checkState(!Strings.isNullOrEmpty(defaultName));

			// create CityDataSource bean
			BeanDefinitionBuilder bdb = BeanDefinitionBuilder.rootBeanDefinition(DynamicDataSource.class);
			bdb.addPropertyReference("defaultTargetDataSource", defaultName);
			bdb.addPropertyValue("targetDataSources", mm);
			registry.registerBeanDefinition("dynamicDataSource", bdb.getBeanDefinition());
		}
	}

	private AbstractBeanDefinition dataSourceBeanDifinition(String beanKey, DataSourceInfo dsi) {
		BeanDefinitionBuilder bdb = BeanDefinitionBuilder.rootBeanDefinition(BasicDataSource.class);

		bdb.getBeanDefinition().setAttribute("id", beanKey);
		bdb.addPropertyValue("driverClassName", "com.mysql.cj.jdbc.Driver");
		bdb.addPropertyValue("url", dsi.connUrl);
		bdb.addPropertyValue("username", dsi.userName);
		bdb.addPropertyValue("password", dsi.password);

		log.info("{} url：{}", beanKey, dsi.connUrl);
		log.info("{} username：{}", beanKey, dsi.userName);
		log.info("{} password：{}", beanKey, dsi.password);

		return bdb.getBeanDefinition();
	}

	private Map<String, DataSourceInfo> parsePropertiesFile() {
		Map<String, DataSourceInfo> mds = new HashMap<>(16);
		InputStream ips2;
		try {
			File cityfile = new File(citiesFilename);
			if (cityfile.exists()) {
				ips2 = new FileInputStream(cityfile);
				log.info("load {} from file path", citiesFilename);
			} else {
				ClassPathResource cpr = new ClassPathResource(citiesFilename);
				ips2 = cpr.getInputStream();
				log.info("load {} from classpath", citiesFilename);
			}

			InputStreamReader isr = new InputStreamReader(ips2, "UTF-8");
			BufferedReader br = new BufferedReader(isr);

			String line;
			while ((line = br.readLine()) != null) {
				String[] keyVal = line.split(".url=");
				DataSourceInfo dsi = new DataSourceInfo();
				List<String> connStr = Splitter.on("|").splitToList(keyVal[1]);

				dsi.connUrl = connStr.get(0);
				dsi.userName = connStr.get(1).split("=")[1];
				dsi.password = connStr.get(2).split("=")[1];
				if (connStr.size() == 4) {
					dsi.transaction = connStr.get(3).split("=")[1];
				} else {
					dsi.transaction = "false";
				}
				mds.put(keyVal[0], dsi);
			}
			br.close();
			isr.close();
			ips2.close();
		} catch (Exception e) {
			log.error("Failed to load city database definition:", e);
		}
		return mds;
	}

	static private class DataSourceInfo {

		public String connUrl;
		public String userName;
		public String password;
		public String transaction;

		@Override
		public String toString() {
			return "DataSourceInfo [connUrl=" + connUrl + ", userName=" + userName + ", password=" + password
					+ ", transaction=" + transaction + "]";
		}

	}

	@Override
	public void setEnvironment(Environment env) {
		citiesFilename = env.getProperty("db.cities.filename", "db.properties");
	}

}
